package com.jintian.smart.platform.system.service;

import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import com.jintian.smart.base.api.message.constant.MailTemplateCodeEnum;
import com.jintian.smart.base.api.message.provider.IMailProvider;
import com.jintian.smart.base.api.security.constant.LoginLogResultEnum;
import com.jintian.smart.base.api.security.entity.LoginFailEntity;
import com.jintian.smart.base.api.security.entity.LoginLogEntity;
import com.jintian.smart.base.api.security.provider.*;
import com.jintian.smart.base.api.security.view.LoginLogVO;
import com.jintian.smart.base.api.system.service.ILoginService;
import com.jintian.smart.base.api.system.view.DepartmentVO;
import com.jintian.smart.base.api.system.view.LoginResultVO;
import com.jintian.smart.base.api.system.view.RoleVO;
import com.jintian.smart.base.api.system.view.MenuVO;
import com.jintian.smart.kernel.common.util.SmartBeanUtil;
import com.jintian.smart.kernel.common.util.SmartEnumUtil;
import com.jintian.smart.kernel.common.util.SmartIpUtil;
import com.jintian.smart.kernel.common.util.SmartStringUtil;
import com.jintian.smart.kernel.core.code.UserErrorCode;
import com.jintian.smart.kernel.core.constant.LoginDeviceEnum;
import com.jintian.smart.kernel.core.constant.RedisKeyConst;
import com.jintian.smart.kernel.core.constant.RequestHeaderConst;
import com.jintian.smart.kernel.core.constant.StringConst;
import com.jintian.smart.kernel.core.domain.RequestUser;
import com.jintian.smart.kernel.core.domain.ResponseDTO;
import com.jintian.smart.kernel.core.domain.UserPermission;
import com.jintian.smart.kernel.core.enumeration.UserTypeEnum;

import com.jintian.smart.kernel.core.system.ConfigKeyEnum;
import com.jintian.smart.base.api.system.view.CaptchaVO;
import com.jintian.smart.kernel.core.system.RequestEmployee;
import com.jintian.smart.base.api.system.entity.EmployeeEntity;
import com.jintian.smart.base.api.system.form.LoginForm;
import com.jintian.smart.base.api.system.constant.AdminCacheConst;
import com.jintian.smart.kernel.oss.service.IFileStorageService;
import com.jintian.smart.kernel.redis.core.RedisService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 登录
 */
@Slf4j
@Service
public class LoginService implements StpInterface, ILoginService {
    /**
     * 万能密码的 sa token loginId 前缀
     */
    private static final String SUPER_PASSWORD_LOGIN_ID_PREFIX = "S";

    @Resource
    private EmployeeService employeeService;

    @Resource
    private CaptchaService captchaService;

    @Resource
    private ConfigService configService;

    @Resource
    private RoleEmployeeService roleEmployeeService;

    @Resource
    private RoleMenuService roleMenuService;

    @Resource
    private RedisService redisService;

    @Resource
    private DepartmentService departmentService;

    @Resource
    private IFileStorageService fileStorageService;

    @DubboReference
    private ILoginLogProvider loginLogProvider;

    @DubboReference
    private ISecurityLoginProvider securityLoginProvider;

    @DubboReference
    private ISecurityPasswordProvider securityPasswordProvider;

    @DubboReference
    private IApiEncryptProvider apiEncryptProvider;

    @DubboReference
    private ILevel3ProtectConfigProvider level3ProtectConfigProvider;

    @DubboReference
    private IMailProvider mailProvider;


    /**
     * 获取验证码
     */
    public ResponseDTO<CaptchaVO> getCaptcha() {
        return ResponseDTO.ok(captchaService.generateCaptcha());
    }

    /**
     * 员工登录
     *
     * @return 返回用户登录信息
     */
    public ResponseDTO<LoginResultVO> login(LoginForm loginForm, String ip, String userAgent) {
        LoginDeviceEnum loginDeviceEnum = SmartEnumUtil.getEnumByValue(loginForm.getLoginDevice(), LoginDeviceEnum.class);
        if (loginDeviceEnum == null) {
            return ResponseDTO.userErrorParam("登录设备暂不支持！");
        }
        // 校验 图形验证码
        ResponseDTO<String> checkCaptcha = captchaService.checkCaptcha(loginForm);
        if (!checkCaptcha.getOk()) {
            return ResponseDTO.error(UserErrorCode.PARAM_ERROR, checkCaptcha.getMsg());
        }
        // 验证登录名
        EmployeeEntity employeeEntity = employeeService.getByLoginName(loginForm.getLoginName());
        if (null == employeeEntity) {
            return ResponseDTO.userErrorParam("登录名或密码错误！");
        }
        // 验证账号状态
        if (employeeEntity.getDeletedFlag() == 1) {
            saveLoginLog(employeeEntity, ip, userAgent, "账号已删除", LoginLogResultEnum.LOGIN_FAIL, loginDeviceEnum);
            return ResponseDTO.userErrorParam("您的账号已被删除,请联系工作人员！");
        }
        if (employeeEntity.getDisabledFlag() == 1) {
            saveLoginLog(employeeEntity, ip, userAgent, "账号已禁用", LoginLogResultEnum.LOGIN_FAIL, loginDeviceEnum);
            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员！");
        }
        // 解密前端加密的密码
        String requestPassword = apiEncryptProvider.decrypt(loginForm.getPassword());
        // 验证密码 是否为万能密码
        String superPassword = configService.getConfigValue(ConfigKeyEnum.SUPER_PASSWORD);
        boolean superPasswordFlag = superPassword.equals(requestPassword);
        // 校验双因子登录
        ResponseDTO<String> validateEmailCode = validateEmailCode(loginForm, employeeEntity, superPasswordFlag);
        if (!validateEmailCode.getOk()) {
            return ResponseDTO.error(validateEmailCode);
        }
        // 万能密码特殊操作
        if (superPasswordFlag) {
            // 对于万能密码：受限制sa token 要求loginId唯一，万能密码只能插入一段uuid
            String saTokenLoginId = SUPER_PASSWORD_LOGIN_ID_PREFIX + StringConst.COLON + UUID.randomUUID().toString().replace("-", "") + StringConst.COLON + employeeEntity.getEmployeeId();
            // 万能密码登录只能登录30分钟
            StpUtil.login(saTokenLoginId, 1800);
        } else {
            // 按照等保登录要求，进行登录失败次数校验
            ResponseDTO<LoginFailEntity> loginFailEntityResponseDTO = securityLoginProvider.checkLogin(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);
            if (!loginFailEntityResponseDTO.getOk()) {
                return ResponseDTO.error(loginFailEntityResponseDTO);
            }
            // 密码错误
            if (!securityPasswordProvider.matchesPwd(employeeService.generateSaltPassword(requestPassword, employeeEntity.getEmployeeUid()), employeeEntity.getLoginPwd())) {
                // 记录登录失败
                saveLoginLog(employeeEntity, ip, userAgent, "密码错误", LoginLogResultEnum.LOGIN_FAIL, loginDeviceEnum);
                // 记录等级保护次数
                String msg = securityLoginProvider.recordLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, employeeEntity.getLoginName(), loginFailEntityResponseDTO.getData());
                return msg == null ? ResponseDTO.userErrorParam("登录名或密码错误！") : ResponseDTO.error(UserErrorCode.LOGIN_FAIL_WILL_LOCK, msg);
            }
            String saTokenLoginId = UserTypeEnum.ADMIN_EMPLOYEE.getValue() + StringConst.COLON + employeeEntity.getEmployeeId();
            // 登录
            StpUtil.login(saTokenLoginId, String.valueOf(loginDeviceEnum.getDesc()));
            // 移除邮箱验证码
            deleteEmailCode(employeeEntity.getEmployeeId());
        }

        // 获取登录结果信息
        String token = StpUtil.getTokenValue();
        // 获取员工信息
        RequestEmployee requestEmployee = this.loadLoginInfo(employeeEntity);
        // 移除登录失败
        securityLoginProvider.removeLoginFail(employeeEntity.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE);

        LoginResultVO loginResultVO = getLoginResult(requestEmployee, token);
        //保存登录记录
        saveLoginLog(employeeEntity, ip, userAgent, superPasswordFlag ? "万能密码登录" : StringConst.EMPTY, LoginLogResultEnum.LOGIN_SUCCESS, loginDeviceEnum);
        // 设置 token
        loginResultVO.setToken(token);
        // 更新用户权限
        this.loadUserPermission(employeeEntity.getEmployeeId());
        return ResponseDTO.ok(loginResultVO);
    }


    /**
     * 获取登录结果信息
     */
    public LoginResultVO getLoginResult(RequestEmployee requestEmployee, String token) {
        // 基础信息
        LoginResultVO loginResultVO = SmartBeanUtil.copy(requestEmployee, LoginResultVO.class);
        // 前端菜单和功能点清单
        List<RoleVO> roleList = roleEmployeeService.getRoleIdList(requestEmployee.getEmployeeId());
        List<MenuVO> menuAndPointsList = roleMenuService.getMenuList(roleList.stream().map(RoleVO::getRoleId).collect(Collectors.toList()), requestEmployee.getAdministratorFlag());
        loginResultVO.setMenuList(menuAndPointsList);
        // 上次登录信息
        LoginLogVO loginLogVO = loginLogProvider.queryLastByUserId(requestEmployee.getEmployeeId(), UserTypeEnum.ADMIN_EMPLOYEE, LoginLogResultEnum.LOGIN_SUCCESS);
        if (loginLogVO != null) {
            loginResultVO.setLastLoginIp(loginLogVO.getLoginIp());
            loginResultVO.setLastLoginIpRegion(loginLogVO.getLoginIpRegion());
            loginResultVO.setLastLoginTime(loginLogVO.getCreateTime());
            loginResultVO.setLastLoginUserAgent(loginLogVO.getUserAgent());
        }
        // 是否需要强制修改密码
        boolean needChangePasswordFlag = securityPasswordProvider.checkNeedChangePassword(requestEmployee.getUserType().getValue(), requestEmployee.getUserId());
        loginResultVO.setNeedUpdatePwdFlag(needChangePasswordFlag ? 1 : 0);
        // 万能密码登录，则不需要设置强制修改密码
        String loginIdByToken = (String) StpUtil.getLoginIdByToken(token);
        if (loginIdByToken != null && loginIdByToken.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) {
            loginResultVO.setNeedUpdatePwdFlag(0);
        }
        return loginResultVO;
    }

    /**
     * 根据登录token 获取员请求工信息
     */
    public RequestEmployee getLoginEmployee(String loginId, HttpServletRequest request) {
        if (loginId == null) {
            return null;
        }
        Long requestEmployeeId = getEmployeeIdByLoginId(loginId);
        if (requestEmployeeId == null) {
            return null;
        }
        RequestEmployee requestEmployee = this.getRequestEmployee(requestEmployeeId);
        // 更新请求ip和user agent
        requestEmployee.setUserAgent(JakartaServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT));
        requestEmployee.setIp(JakartaServletUtil.getClientIP(request));
        return requestEmployee;
    }

    /**
     * 根据 loginId 获取 员工id
     */
    Long getEmployeeIdByLoginId(String loginId) {
        if (loginId == null) {
            return null;
        }
        try {
            // 如果是 万能密码 登录的用户
            String employeeIdStr = null;
            if (loginId.startsWith(SUPER_PASSWORD_LOGIN_ID_PREFIX)) {
                employeeIdStr = loginId.split(StringConst.COLON)[2];
            } else {
                employeeIdStr = loginId.substring(2);
            }
            return Long.parseLong(employeeIdStr);
        } catch (Exception e) {
            log.error("loginId parse error , loginId : {}", loginId, e);
            return null;
        }
    }

    /**
     * 退出登录
     */
    public ResponseDTO<String> logout(RequestUser requestUser) {
        // sa token 登出
        StpUtil.logout();
        // 清空登录信息缓存
        this.clear();
        //保存登出日志
        LoginLogEntity loginEntity = LoginLogEntity.builder()
                .userId(requestUser.getUserId())
                .userType(requestUser.getUserType().getValue())
                .userName(requestUser.getUserName())
                .userAgent(requestUser.getUserAgent())
                .loginIp(requestUser.getIp())
                .loginIpRegion(SmartIpUtil.getRegion(requestUser.getIp()))
                .loginResult(LoginLogResultEnum.LOGIN_OUT.getValue())
                .createTime(LocalDateTime.now())
                .build();
        loginLogProvider.log(loginEntity);
        return ResponseDTO.ok();
    }

    /**
     * 保存登录日志
     */
    private void saveLoginLog(EmployeeEntity employeeEntity, String ip, String userAgent, String remark, LoginLogResultEnum result, LoginDeviceEnum loginDeviceEnum) {
        LoginLogEntity loginEntity = LoginLogEntity.builder()
                .userId(employeeEntity.getEmployeeId())
                .userType(UserTypeEnum.ADMIN_EMPLOYEE.getValue())
                .userName(employeeEntity.getActualName())
                .userAgent(userAgent)
                .loginIp(ip)
                .loginIpRegion(SmartIpUtil.getRegion(ip))
                .remark(remark)
                .loginDevice(loginDeviceEnum.getDesc())
                .loginResult(result.getValue())
                .createTime(LocalDateTime.now())
                .build();
        loginLogProvider.log(loginEntity);
    }


    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        Long employeeId = this.getEmployeeIdByLoginId((String) loginId);
        if (employeeId == null) {
            return Collections.emptyList();
        }

        UserPermission userPermission = this.getUserPermission(employeeId);
        return userPermission.getPermissionList();
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        Long employeeId = this.getEmployeeIdByLoginId((String) loginId);
        if (employeeId == null) {
            return Collections.emptyList();
        }

        UserPermission userPermission = this.getUserPermission(employeeId);
        return userPermission.getRoleList();
    }

    /**
     * 发送 邮箱 验证码
     */
    public ResponseDTO<String> sendEmailCode(String loginName) {
        // 开启双因子登录
        if (!level3ProtectConfigProvider.isTwoFactorLoginEnabled()) {
            return ResponseDTO.userErrorParam("无需使用邮箱验证码");
        }
        // 验证登录名
        EmployeeEntity employeeEntity = employeeService.getByLoginName(loginName);
        if (null == employeeEntity) {
            return ResponseDTO.ok();
        }
        // 验证账号状态
        if (employeeEntity.getDeletedFlag() == 1) {
            return ResponseDTO.userErrorParam("您的账号已被删除,请联系工作人员！");
        }
        if (employeeEntity.getDisabledFlag() == 1) {
            return ResponseDTO.userErrorParam("您的账号已被禁用,请联系工作人员！");
        }
        String mail = employeeEntity.getEmail();
        if (SmartStringUtil.isBlank(mail)) {
            return ResponseDTO.userErrorParam("您暂未配置邮箱地址，请联系管理员配置邮箱");
        }
        // 校验验证码发送时间，60秒内不能重复发生
        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
        String emailCode = redisService.get(redisVerificationCodeKey);
        long sendCodeTimeMills = -1;
        if (!SmartStringUtil.isEmpty(emailCode)) {
            sendCodeTimeMills = NumberUtil.parseLong(emailCode.split(StringConst.UNDERLINE)[1]);
        }
        if (System.currentTimeMillis() - sendCodeTimeMills < 60 * 1000) {
            return ResponseDTO.userErrorParam("邮箱验证码已发送，一分钟内请勿重复发送");
        }
        //生成验证码
        long currentTimeMillis = System.currentTimeMillis();
        String verificationCode = RandomUtil.randomNumbers(4);
        redisService.set(redisVerificationCodeKey, verificationCode + StringConst.UNDERLINE + currentTimeMillis, 300);
        // 发送邮件验证码
        HashMap<String, Object> mailParams = new HashMap<>();
        mailParams.put("code", verificationCode);
        return mailProvider.sendMail(MailTemplateCodeEnum.LOGIN_VERIFICATION_CODE, mailParams, Collections.singletonList(employeeEntity.getEmail()));
    }

    /**
     * 校验邮箱验证码
     */
    private ResponseDTO<String> validateEmailCode(LoginForm loginForm, EmployeeEntity employeeEntity, boolean superPasswordFlag) {
        // 万能密码则不校验
        if (superPasswordFlag) {
            return ResponseDTO.ok();
        }
        // 未开启双因子登录
        if (!level3ProtectConfigProvider.isTwoFactorLoginEnabled()) {
            return ResponseDTO.ok();
        }
        if (SmartStringUtil.isEmpty(loginForm.getEmailCode())) {
            return ResponseDTO.userErrorParam("请输入邮箱验证码");
        }
        // 校验验证码
        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeEntity.getEmployeeId());
        String emailCode = redisService.get(redisVerificationCodeKey);
        if (SmartStringUtil.isEmpty(emailCode)) {
            return ResponseDTO.userErrorParam("邮箱验证码已失效，请重新发送");
        }
        if (!emailCode.split(StringConst.UNDERLINE)[0].equals(loginForm.getEmailCode().trim())) {
            return ResponseDTO.userErrorParam("邮箱验证码错误，请重新填写");
        }
        return ResponseDTO.ok();
    }

    /**
     * 移除邮箱验证码
     */
    private void deleteEmailCode(Long employeeId) {
        String redisVerificationCodeKey = redisService.generateRedisKey(RedisKeyConst.Support.LOGIN_VERIFICATION_CODE, UserTypeEnum.ADMIN_EMPLOYEE.getValue() + RedisKeyConst.SEPARATOR + employeeId);
        redisService.delete(redisVerificationCodeKey);
    }

    public void clearLoginEmployeeCache(Long employeeId) {
        this.clear();
    }


    /**
     * 获取请求用户信息
     */
    @Cacheable(AdminCacheConst.Login.REQUEST_EMPLOYEE)
    public RequestEmployee getRequestEmployee(Long requestEmployeeId) {
        if (requestEmployeeId == null) {
            return null;
        }
        // 员工基本信息
        EmployeeEntity employeeEntity = employeeService.getById(requestEmployeeId);
        if (employeeEntity == null) {
            return null;
        }

        return this.loadLoginInfo(employeeEntity);
    }

    /**
     * 获取登录的用户信息
     */
    @CachePut(value = AdminCacheConst.Login.REQUEST_EMPLOYEE, key = "#employeeEntity.employeeId")
    public RequestEmployee loadLoginInfo(EmployeeEntity employeeEntity) {
        // 基础信息
        RequestEmployee requestEmployee = SmartBeanUtil.copy(employeeEntity, RequestEmployee.class);
        requestEmployee.setUserType(UserTypeEnum.ADMIN_EMPLOYEE);

        // 部门信息
        DepartmentVO department = departmentService.getDepartmentById(employeeEntity.getDepartmentId());
        requestEmployee.setDepartmentName(null == department ? StringConst.EMPTY : department.getDepartmentName());

        // 头像信息
        String avatar = employeeEntity.getAvatar();
        if (StringUtils.isNotBlank(avatar)) {
            ResponseDTO<String> getFileUrl = fileStorageService.getFileUrl(avatar);
            if (BooleanUtils.isTrue(getFileUrl.getOk())) {
                requestEmployee.setAvatar(getFileUrl.getData());
            }
        }
        return requestEmployee;
    }

    /**
     * 获取用户的权限（包含 角色列表、权限列表）
     */
    @Cacheable(AdminCacheConst.Login.USER_PERMISSION)
    public UserPermission getUserPermission(Long employeeId) {
        if (null == employeeId) {
            return null;
        }

        return this.loadUserPermission(employeeId);
    }

    /**
     * 获取用户的权限（包含 角色列表、权限列表）
     */
    @CachePut(AdminCacheConst.Login.USER_PERMISSION)
    public UserPermission loadUserPermission(Long employeeId) {
        UserPermission userPermission = new UserPermission();
        userPermission.setPermissionList(new ArrayList<>());
        userPermission.setRoleList(new ArrayList<>());

        // 角色列表
        List<RoleVO> roleList = roleEmployeeService.getRoleIdList(employeeId);
        userPermission.getRoleList().addAll(roleList.stream().map(RoleVO::getRoleCode).collect(Collectors.toSet()));

        // 前端菜单和功能点清单
        EmployeeEntity employeeEntity = employeeService.getById(employeeId);

        List<MenuVO> menuAndPointsList = roleMenuService.getMenuList(roleList.stream().map(RoleVO::getRoleId).collect(Collectors.toList()), employeeEntity.getAdministratorFlag());

        // 权限列表
        HashSet<String> permissionSet = new HashSet<>();
        for (MenuVO menu : menuAndPointsList) {
            if (menu.getPermsType() == null) {
                continue;
            }

            String perms = menu.getApiPerms();
            if (StringUtils.isEmpty(perms)) {
                continue;
            }
            //接口权限
            String[] split = perms.split(",");
            permissionSet.addAll(Arrays.asList(split));
        }
        userPermission.getPermissionList().addAll(permissionSet);

        return userPermission;
    }


    @CacheEvict(value = {AdminCacheConst.Login.USER_PERMISSION, AdminCacheConst.Login.REQUEST_EMPLOYEE}, allEntries = true)
    public void clear() {
    }
}
